package pwd.allen;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
import java.util.Date;

/**
 * JVM 会优先加载带 Instrumentation 签名的方法，如果没有该方法，才会加载另一种不带Instrumentation的
 *
 * @author 门那粒沙
 * @create 2021-03-08 23:23
 **/
public class PreMainAgent {

    /**
     *
     * 执行时机：在main方法之前执行
     * JVM 会优先加载 带 Instrumentation 签名的方法，加载成功忽略第二种，如果第一种没有，则加载第二种方法
     * 代码见{@link sun.instrument.InstrumentationImpl#loadClassAndStartAgent}
     *
     * 应用场景：拦截用户类的加载，结合第三方的字节码编译工具，比如ASM，javassist，cglib等等来改写实现类
     *
     * @param agentArgs
     * @param inst
     */
    public static void premain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
        // Date是内部类，并没有被拦截，这里想重新出发拦截，但没有效果 TODO
        inst.retransformClasses(Date.class);

        System.out.println("【premain】参数：" + agentArgs);
        System.out.println("isRedefineClassesSupported:" + inst.isRedefineClassesSupported());
        System.out.println("isRetransformClassesSupported:" + inst.isRetransformClassesSupported());

        // 增加一个Class 文件的转换器，转换器用于改变 Class 二进制流的数据，参数 canRetransform 设置是否允许重新转换。
        // 在类加载之前，重新定义 Class 文件，ClassDefinition 表示对一个类新的定义，如果在类加载之后，需要使用 retransformClasses 方法重新定义。
        // addTransformer方法配置之后，后续的类加载都会被Transformer拦截。
        // 对于已经加载过的类，可以执行retransformClasses来重新触发这个Transformer的拦截。类加载的字节码被修改后，除非再次被retransform，否则不会恢复。
        inst.addTransformer(new DefineTransformer("premain"), true);

        System.out.println(new Date());
    }
//    /**
//     * @param agentArgs
//     */
//    public static void premain(String agentArgs) {
//        System.out.println("premain允许，参数：" + agentArgs);
//    }

    /**
     * 执行时机：在main方法之后运行
     * 采用attach机制，被代理的目标程序VM有可能很早之前已经启动，当然其所有类已经被加载完成，
     * 这个时候需要借助 {@link Instrumentation#retransformClasses(Class[])} 让对应的类可以重新转换，从而激活重新转换的类执行ClassFileTransformer列表中的回调
     *
     * @param agentArgs
     * @param inst
     */
    public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
        System.out.println("【agentmain】参数：" + agentArgs);
        inst.addTransformer(new DefineTransformer("agentmain"), true);
        inst.retransformClasses(Date.class); // 没效果
    }
//    public static void agentmain (String agentArgs) {}


}

/**
 * 定义一个class文件转换器，简单地打印加载的类名
 * 部分类不会被加载到，遗漏的主要是系统类，因为很多系统类先于 agent 执行
 *
 * 使用场景：可以借助字节码编译工具对拦截的类进行改写
 */
class DefineTransformer implements ClassFileTransformer {

    private String desc;

    public DefineTransformer(String desc) {
        this.desc = desc;
    }

    /**
     * 当字节码将要被加载之前会先进入该方法
     * @param className 将要加载的类的全限定名，如：java/util/Date
     * @return 如果需要增强，则返回增强后的字节码，否则返回Null或者classfileBuffer
     */
    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined
            , ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        System.out.println(String.format("【%s】loader:%s----load Class:%s", desc, loader.getClass().getName(), className));

        if ("java/util/Date".equals(className)) {
            System.out.println("拦截到java.util.Date类");
        }

        // 如果返回null则字节码不会被修改
        return classfileBuffer;
    }
}
